import { Logger } from "../../common/log/Logger";


export const INJECT_CLASS_NAME_TAG = '__inject_clazz_name__';

export const INJECT_INFO_LIST_TAG = '__inject_info_list__';

export const INJECT_PRESENT_LIST_TAG = '__inject_present_list__';

export const INJECT_MODEL_LIST_TAG = '__inject_model_list__';

export type Constructor<T = unknown> = new (...args: any[]) => T;


const _nameToClass: Record<string, Constructor> = createMap(true);

function createMap(forceDictMode?: boolean): any {
    const map = Object.create(null);
    if (forceDictMode) {
        const INVALID_IDENTIFIER_1 = '.';
        const INVALID_IDENTIFIER_2 = '/';
        // assign dummy values on the object
        map[INVALID_IDENTIFIER_1] = 1;
        map[INVALID_IDENTIFIER_2] = 1;
        delete map[INVALID_IDENTIFIER_1];
        delete map[INVALID_IDENTIFIER_2];
    }
    return map;
}

const value = (() => {
    const descriptor: PropertyDescriptor = {
        value: undefined,
        enumerable: false,
        writable: false,
        configurable: true,
    };
    return (object: Record<string | number, any>, propertyName: string, value_: any, writable?: boolean, enumerable?: boolean) => {
        descriptor.value = value_;
        descriptor.writable = writable;
        descriptor.enumerable = enumerable;
        Object.defineProperty(object, propertyName, descriptor);
        descriptor.value = undefined;
    };
})();

function setup(tag: string, table: Record<string | number, any>, allowExist: boolean) {
    return function (id: string, constructor: Constructor) {
        if (constructor.prototype.hasOwnProperty(tag)) {
            delete table[constructor.prototype[tag]];
        }

        value(constructor.prototype, tag, id);

        if (id) {
            const registered = table[id];
            if (!allowExist && registered && registered !== constructor) {
                let err = `A Class already exists with the same ${tag} : "${id}".`;
                Logger.error(err)
            } else {
                table[id] = constructor;
            }
        }
    }
}

const doSetClassName = setup(INJECT_CLASS_NAME_TAG, _nameToClass, false);

export function setClass(className: string, constructor: Constructor) {
    doSetClassName(className, constructor);
}

export function getClass(className: string) {
    return _nameToClass[className];
}

export function delClass(className: string) {
    delete _nameToClass[className];
}


export interface Abstract<T> {
    prototype: T;
}

export type ServiceIdentifier<T = unknown> = (string | symbol | Constructor<T> | Abstract<T>);

export class LazyServiceIdentifer<T = unknown> {
    private _cb: () => ServiceIdentifier<T>;
    public constructor(cb: () => ServiceIdentifier<T>) {
        this._cb = cb;
    }

    public unwrap() {
        return this._cb();
    }
}